import {NextFunction, Request, Response} from 'express';
import glob from 'glob';
import _ from 'lodash';
import {resolve} from 'path';

import Log from './log';

const pathPrefix = Symbol('pathPrefix');

interface Route {
    target: any;
    method: string;
    options?: Options;
    path: string;
    callback: (request: any, response: any) => any;
}

interface WebApiRequest extends Request {
    user?: any;
}

// tslint:disable-next-line:no-empty-interface
interface WebApiResponse extends Response {
}

interface Options {
    auth: boolean;
}

const defaultOptions: Options = {
    auth: false,
};

class AuthError extends Error {
}

@Log
class RouteManager {

    private static async authorizeWrap(
        options: Options,
        [req, res, next]: [WebApiRequest, WebApiResponse, NextFunction],
        target: any,
    ): Promise<any> {

        if (options.auth && !req.user && !req.user.isAuthenticated) {
            throw new AuthError('Current user is not authenticated.');
        }
        return target(req, res, next);
    }

    private readonly app: any;
    private readonly routesPath: string;
    private readonly routes: Route[];
    private readonly logger: any;

    constructor(app: any, routesPath: string) {
        this.app = app;
        this.routesPath = routesPath;
        this.routes = [];
    }

    public init(): void {
        glob.sync(resolve(this.routesPath, `./*.${process.env.NODE_ENV !== 'production' ? 'ts' : 'js'}`)).forEach(require);
        _.forEach(this.routes, this.wrap.bind(this));
    }

    public register([method, path, options]: any[], target: any, key: any, descriptor: any): any {
        this.routes.push({target, method, options, callback: target[key], path});
        return descriptor;
    }

    private wrap(route: Route): void {
        const {target, method, path, callback, options} = route;
        const o = _.assign(defaultOptions, options);

        const prefix = target[pathPrefix] || '';
        this.app[method](prefix + path, (req: WebApiRequest, res: WebApiResponse, next: NextFunction) => {
            const start = new Date();
            const apiDescription = `${req.method} ${req.originalUrl}`;
            return RouteManager.authorizeWrap(o, [req, res, next], callback)
                .catch((err) => {
                    if (err instanceof AuthError) {
                        this.logger.log(`Access api ${apiDescription} failed with auth error ${err.stack}`);
                        res.status(401).send(err.message);
                    } else {
                        this.logger.log(`Access api ${apiDescription} failed with error ${err.stack}`);
                        res.status(500).send(err.message);
                    }
                }).then(() => {
                    const times: number = new Date().getTime() - start.getTime();
                    this.logger.log(`Access api ${apiDescription} done during ${times / 1000}s`);
                });
        });
    }
}

let manger: RouteManager;

function setup(app: any) {
    manger = new RouteManager(app, resolve(__dirname, '../controller'));
    manger.init();
}

function Controller(path: string): any {
    return (target: any, key: string, descriptor: any) => {
        target.prototype[pathPrefix] = path || '';
        return descriptor;
    };
}

function GET(path: string, options?: Options): any {
    return manger?.register.bind(manger, ['get', path, options]);
}

function POST(path: string, options?: Options): any {
    return manger?.register.bind(manger, ['post', path, options]);
}

interface Roles {
    name: string,
    OrganizationId: number,
    category: string
}

interface User {
    id: number,
    displayName: string,
    isAuthenticated: boolean,
    XTid?: number,
    code?: string,
    name: string,
    avatar?: string,
    roles: Roles[],
    OrganizationId: number,
    OrganizationName?: string,
    OrganizationPath?: string,
    OrganizationLevel?: number,
    PartyOrganizationId?: number,
    LeagueOrganizationId?: number,
    UnionOrganizationId?: number,
}

export {
    setup,
    Controller,
    GET,
    POST,
    WebApiRequest,
    WebApiResponse,
    User
};
